// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/aura/window_targeter.h"

#include <utility>

#include "base/macros.h"
#include "ui/aura/scoped_window_targeter.h"
#include "ui/aura/test/aura_test_base.h"
#include "ui/aura/test/test_window_delegate.h"
#include "ui/aura/window.h"
#include "ui/events/event_utils.h"
#include "ui/events/test/test_event_handler.h"

namespace aura {

// Always returns the same window.
class StaticWindowTargeter : public WindowTargeter {
public:
    explicit StaticWindowTargeter(aura::Window* window)
        : window_(window)
    {
    }
    ~StaticWindowTargeter() override { }

private:
    // aura::WindowTargeter:
    Window* FindTargetForLocatedEvent(Window* window,
        ui::LocatedEvent* event) override
    {
        return window_;
    }

    Window* window_;

    DISALLOW_COPY_AND_ASSIGN(StaticWindowTargeter);
};

class WindowTargeterTest : public test::AuraTestBase {
public:
    WindowTargeterTest() { }
    ~WindowTargeterTest() override { }

    Window* root_window() { return AuraTestBase::root_window(); }
};

gfx::RectF GetEffectiveVisibleBoundsInRootWindow(Window* window)
{
    gfx::RectF bounds = gfx::RectF(gfx::SizeF(window->bounds().size()));
    Window* root = window->GetRootWindow();
    CHECK(window->layer());
    CHECK(root->layer());
    gfx::Transform transform;
    if (!window->layer()->GetTargetTransformRelativeTo(root->layer(), &transform))
        return gfx::RectF();
    transform.TransformRect(&bounds);
    return bounds;
}

TEST_F(WindowTargeterTest, Basic)
{
    test::TestWindowDelegate delegate;
    scoped_ptr<Window> window(CreateNormalWindow(1, root_window(), &delegate));
    Window* one = CreateNormalWindow(2, window.get(), &delegate);
    Window* two = CreateNormalWindow(3, window.get(), &delegate);

    window->SetBounds(gfx::Rect(0, 0, 100, 100));
    one->SetBounds(gfx::Rect(0, 0, 500, 100));
    two->SetBounds(gfx::Rect(501, 0, 500, 1000));

    root_window()->Show();

    ui::test::TestEventHandler handler;
    one->AddPreTargetHandler(&handler);

    ui::MouseEvent press(ui::ET_MOUSE_PRESSED, gfx::Point(20, 20),
        gfx::Point(20, 20), ui::EventTimeForNow(), ui::EF_NONE,
        ui::EF_NONE);
    DispatchEventUsingWindowDispatcher(&press);
    EXPECT_EQ(1, handler.num_mouse_events());

    handler.Reset();
    DispatchEventUsingWindowDispatcher(&press);
    EXPECT_EQ(1, handler.num_mouse_events());

    one->RemovePreTargetHandler(&handler);
}

TEST_F(WindowTargeterTest, ScopedWindowTargeter)
{
    test::TestWindowDelegate delegate;
    scoped_ptr<Window> window(CreateNormalWindow(1, root_window(), &delegate));
    Window* child = CreateNormalWindow(2, window.get(), &delegate);

    window->SetBounds(gfx::Rect(30, 30, 100, 100));
    child->SetBounds(gfx::Rect(20, 20, 50, 50));
    root_window()->Show();

    ui::EventTarget* root = root_window();
    ui::EventTargeter* targeter = root->GetEventTargeter();

    gfx::Point event_location(60, 60);
    {
        ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, event_location, event_location,
            ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
        EXPECT_EQ(child, targeter->FindTargetForEvent(root, &mouse));
    }

    // Install a targeter on |window| so that the events never reach the child.
    scoped_ptr<ScopedWindowTargeter> scoped_targeter(
        new ScopedWindowTargeter(window.get(), scoped_ptr<ui::EventTargeter>(new StaticWindowTargeter(window.get()))));
    {
        ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, event_location, event_location,
            ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
        EXPECT_EQ(window.get(), targeter->FindTargetForEvent(root, &mouse));
    }
    scoped_targeter.reset();
    {
        ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, event_location, event_location,
            ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
        EXPECT_EQ(child, targeter->FindTargetForEvent(root, &mouse));
    }
}

// Test that ScopedWindowTargeter does not crash if the window for which it
// replaces the targeter gets destroyed before it does.
TEST_F(WindowTargeterTest, ScopedWindowTargeterWindowDestroyed)
{
    test::TestWindowDelegate delegate;
    scoped_ptr<Window> window(CreateNormalWindow(1, root_window(), &delegate));
    scoped_ptr<ScopedWindowTargeter> scoped_targeter(
        new ScopedWindowTargeter(window.get(), scoped_ptr<ui::EventTargeter>(new StaticWindowTargeter(window.get()))));

    window.reset();
    scoped_targeter.reset();

    // We did not crash!
}

TEST_F(WindowTargeterTest, TargetTransformedWindow)
{
    root_window()->Show();

    test::TestWindowDelegate delegate;
    scoped_ptr<Window> window(CreateNormalWindow(2, root_window(), &delegate));

    const gfx::Rect window_bounds(100, 20, 400, 80);
    window->SetBounds(window_bounds);

    ui::EventTarget* root_target = root_window();
    ui::EventTargeter* targeter = root_target->GetEventTargeter();
    gfx::Point event_location(490, 50);
    {
        ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, event_location, event_location,
            ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
        EXPECT_EQ(window.get(), targeter->FindTargetForEvent(root_target, &mouse));
    }

    // Scale |window| by 50%. This should move it away from underneath
    // |event_location|, so an event in that location will not be targeted to it.
    gfx::Transform transform;
    transform.Scale(0.5, 0.5);
    window->SetTransform(transform);
    EXPECT_EQ(gfx::RectF(100, 20, 200, 40).ToString(),
        GetEffectiveVisibleBoundsInRootWindow(window.get()).ToString());
    {
        ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, event_location, event_location,
            ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
        EXPECT_EQ(root_window(), targeter->FindTargetForEvent(root_target, &mouse));
    }

    transform = gfx::Transform();
    transform.Translate(200, 10);
    transform.Scale(0.5, 0.5);
    window->SetTransform(transform);
    EXPECT_EQ(gfx::RectF(300, 30, 200, 40).ToString(),
        GetEffectiveVisibleBoundsInRootWindow(window.get()).ToString());
    {
        ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, event_location, event_location,
            ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
        EXPECT_EQ(window.get(), targeter->FindTargetForEvent(root_target, &mouse));
    }
}

class IdCheckingEventTargeter : public WindowTargeter {
public:
    IdCheckingEventTargeter(int id)
        : id_(id)
    {
    }
    ~IdCheckingEventTargeter() override { }

protected:
    // WindowTargeter:
    bool SubtreeShouldBeExploredForEvent(Window* window,
        const ui::LocatedEvent& event) override
    {
        return (window->id() == id_ && WindowTargeter::SubtreeShouldBeExploredForEvent(window, event));
    }

private:
    int id_;
};

TEST_F(WindowTargeterTest, Bounds)
{
    test::TestWindowDelegate delegate;
    scoped_ptr<Window> parent(CreateNormalWindow(1, root_window(), &delegate));
    scoped_ptr<Window> child(CreateNormalWindow(1, parent.get(), &delegate));
    scoped_ptr<Window> grandchild(CreateNormalWindow(1, child.get(), &delegate));

    parent->SetBounds(gfx::Rect(0, 0, 30, 30));
    child->SetBounds(gfx::Rect(5, 5, 20, 20));
    grandchild->SetBounds(gfx::Rect(5, 5, 5, 5));

    ASSERT_EQ(1u, root_window()->children().size());
    ASSERT_EQ(1u, root_window()->children()[0]->children().size());
    ASSERT_EQ(1u, root_window()->children()[0]->children()[0]->children().size());

    Window* parent_r = root_window()->children()[0];
    Window* child_r = parent_r->children()[0];
    Window* grandchild_r = child_r->children()[0];

    ui::EventTarget* root_target = root_window();
    ui::EventTargeter* targeter = root_target->GetEventTargeter();

    // Dispatch a mouse event that falls on the parent, but not on the child. When
    // the default event-targeter used, the event will still reach |grandchild|,
    // because the default targeter does not look at the bounds.
    ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(1, 1), gfx::Point(1, 1),
        ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
    EXPECT_EQ(parent_r, targeter->FindTargetForEvent(root_target, &mouse));

    // Install a targeter on the |child| that looks at the window id as well
    // as the bounds and makes sure the event reaches the target only if the id of
    // the window is equal to 2 (incorrect). This causes the event to get handled
    // by |parent|.
    ui::MouseEvent mouse2(ui::ET_MOUSE_MOVED, gfx::Point(8, 8), gfx::Point(8, 8),
        ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
    scoped_ptr<ui::EventTargeter> original_targeter = child_r->SetEventTargeter(
        scoped_ptr<ui::EventTargeter>(new IdCheckingEventTargeter(2)));
    EXPECT_EQ(parent_r, targeter->FindTargetForEvent(root_target, &mouse2));

    // Now install a targeter on the |child| that looks at the window id as well
    // as the bounds and makes sure the event reaches the target only if the id of
    // the window is equal to 1 (correct).
    ui::MouseEvent mouse3(ui::ET_MOUSE_MOVED, gfx::Point(8, 8), gfx::Point(8, 8),
        ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
    child_r->SetEventTargeter(
        scoped_ptr<ui::EventTargeter>(new IdCheckingEventTargeter(1)));
    EXPECT_EQ(child_r, targeter->FindTargetForEvent(root_target, &mouse3));

    // restore original WindowTargeter for |child|.
    child_r->SetEventTargeter(std::move(original_targeter));

    // Target |grandchild| location.
    ui::MouseEvent second(ui::ET_MOUSE_MOVED, gfx::Point(12, 12),
        gfx::Point(12, 12), ui::EventTimeForNow(), ui::EF_NONE,
        ui::EF_NONE);
    EXPECT_EQ(grandchild_r, targeter->FindTargetForEvent(root_target, &second));

    // Target |child| location.
    ui::MouseEvent third(ui::ET_MOUSE_MOVED, gfx::Point(8, 8), gfx::Point(8, 8),
        ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
    EXPECT_EQ(child_r, targeter->FindTargetForEvent(root_target, &third));
}

class IgnoreWindowTargeter : public WindowTargeter {
public:
    IgnoreWindowTargeter() { }
    ~IgnoreWindowTargeter() override { }

private:
    // WindowTargeter:
    bool SubtreeShouldBeExploredForEvent(Window* window,
        const ui::LocatedEvent& event) override
    {
        return false;
    }
};

// Verifies that an EventTargeter installed on an EventTarget can dictate
// whether the target itself can process an event.
TEST_F(WindowTargeterTest, TargeterChecksOwningEventTarget)
{
    test::TestWindowDelegate delegate;
    scoped_ptr<Window> child(CreateNormalWindow(1, root_window(), &delegate));

    ui::EventTarget* root_target = root_window();
    ui::EventTargeter* targeter = root_target->GetEventTargeter();

    ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(10, 10),
        gfx::Point(10, 10), ui::EventTimeForNow(), ui::EF_NONE,
        ui::EF_NONE);
    EXPECT_EQ(child.get(), targeter->FindTargetForEvent(root_target, &mouse));

    // Install an event targeter on |child| which always prevents the target from
    // receiving event.
    child->SetEventTargeter(
        scoped_ptr<ui::EventTargeter>(new IgnoreWindowTargeter()));

    ui::MouseEvent mouse2(ui::ET_MOUSE_MOVED, gfx::Point(10, 10),
        gfx::Point(10, 10), ui::EventTimeForNow(), ui::EF_NONE,
        ui::EF_NONE);
    EXPECT_EQ(root_window(), targeter->FindTargetForEvent(root_target, &mouse2));
}

} // namespace aura
